# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'Edit group settings', :with_current_organization, feature_category: :groups_and_projects do
  include Spec::Support::Helpers::ModalHelpers
  include Features::WebIdeSpecHelpers
  include SafeFormatHelper
  include ActionView::Helpers::TagHelper
  include Namespaces::DeletableHelper

  let_it_be(:user) { create(:user, organization: current_organization) }
  let_it_be(:admin) { create(:user, :admin, organization: current_organization) }
  let_it_be_with_reload(:group) { create(:group, path: 'foo', owners: [user]) }

  before do
    sign_in(user)
  end

  describe 'when the group path is changed' do
    let(:new_group_path) { 'bar' }
    let(:old_group_full_path) { "/#{group.path}" }
    let(:new_group_full_path) { "/#{new_group_path}" }

    it 'the group is accessible via the new path' do
      update_path(new_group_path)
      visit new_group_full_path

      expect(page).to have_current_path(new_group_full_path, ignore_query: true)
      expect(find('h1.home-panel-title')).to have_content(group.name)
    end

    it 'the old group path redirects to the new path' do
      update_path(new_group_path)
      visit old_group_full_path

      expect(page).to have_current_path(new_group_full_path, ignore_query: true)
      expect(find('h1.home-panel-title')).to have_content(group.name)
    end

    context 'with a subgroup' do
      let!(:subgroup) { create(:group, parent: group, path: 'subgroup') }
      let(:old_subgroup_full_path) { "/#{group.path}/#{subgroup.path}" }
      let(:new_subgroup_full_path) { "/#{new_group_path}/#{subgroup.path}" }

      it 'the subgroup is accessible via the new path' do
        update_path(new_group_path)
        visit new_subgroup_full_path

        expect(page).to have_current_path(new_subgroup_full_path, ignore_query: true)
        expect(find('h1.home-panel-title')).to have_content(subgroup.name)
      end

      it 'the old subgroup path redirects to the new path' do
        update_path(new_group_path)
        visit old_subgroup_full_path

        expect(page).to have_current_path(new_subgroup_full_path, ignore_query: true)
        expect(find('h1.home-panel-title')).to have_content(subgroup.name)
      end
    end

    context 'with a project', :js do
      let!(:project) { create(:project, group: group) }
      let(:old_project_full_path) { "/#{group.path}/#{project.path}" }
      let(:new_project_full_path) { "/#{new_group_path}/#{project.path}" }

      before(:context) do
        TestEnv.clean_test_path
      end

      after do
        TestEnv.clean_test_path
      end

      it 'the project is accessible via the new path' do
        update_path(new_group_path)
        visit new_project_full_path

        expect(page).to have_current_path(new_project_full_path, ignore_query: true)
        expect(find_by_testid('breadcrumb-links')).to have_content(project.name)
      end

      it 'the old project path redirects to the new path' do
        update_path(new_group_path)
        visit old_project_full_path

        expect(page).to have_current_path(new_project_full_path, ignore_query: true)
        expect(find_by_testid('breadcrumb-links')).to have_content(project.name)
      end
    end
  end

  describe 'project creation level menu' do
    it 'shows the selection menu' do
      visit edit_group_path(group)

      expect(page).to have_content('Minimum role required to create projects')
    end
  end

  describe 'subgroup creation level menu' do
    it 'shows the selection menu' do
      visit edit_group_path(group)

      expect(page).to have_content('Roles allowed to create subgroups')
    end
  end

  describe 'edit group avatar' do
    before do
      visit edit_group_path(group)

      attach_file(:group_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))

      expect { save_general_group }.to change { group.reload.avatar? }.to(true)
    end

    it 'uploads new group avatar' do
      expect(group.avatar).to be_instance_of AvatarUploader
      expect(group.avatar.url).to eq "/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif"
      expect(page).to have_link('Remove avatar')
    end

    it 'removes group avatar' do
      expect { click_link 'Remove avatar' }.to change { group.reload.avatar? }.to(false)
      expect(page).not_to have_link('Remove avatar')
    end
  end

  describe 'edit group path' do
    it 'has a root URL label for top-level group' do
      visit edit_group_path(group)

      expect(find(:css, '.group-root-path').text).to eq(unscoped_root_url)
    end

    context 'with scoped paths' do
      before do
        allow(current_organization).to receive(:scoped_paths?).and_return(true)
      end

      it 'has a parent group URL label for a subgroup group' do
        subgroup = create(:group, parent: group)

        visit edit_group_path(subgroup)

        expect(find(:css, '.group-root-path').text).to eq(group_url(subgroup.parent) + '/')
      end
    end

    context 'without scoped paths' do
      before do
        allow(current_organization).to receive(:scoped_paths?).and_return(false)
      end

      it 'has a parent group URL label for a subgroup group' do
        subgroup = create(:group, parent: group)

        visit edit_group_path(subgroup)

        expect(find(:css, '.group-root-path').text).to eq(group_url(subgroup.parent) + '/')
      end
    end
  end

  describe 'transfer group', :js do
    let(:namespace_select) { find_by_testid('transfer-group-namespace-select') }
    let(:confirm_modal) { find_by_testid('confirm-danger-modal') }

    shared_examples 'can transfer the group' do
      before do
        selected_group.add_owner(user)
      end

      it 'can successfully transfer the group' do
        selected_group_path = selected_group.path

        visit edit_group_path(selected_group)

        within_testid('transfer-locations-dropdown') do
          click_button _('Select parent group')
          fill_in _('Search'), with: target_group&.name || ''
          wait_for_requests
          click_button(target_group&.name || 'No parent group')
        end

        click_button s_('GroupSettings|Transfer group')

        page.within(confirm_modal) do
          expect(page).to have_text "You are about to transfer #{selected_group.full_path} to another namespace. This action changes the group's path and can lead to data loss."

          fill_in 'confirm_name_input', with: selected_group.full_path
          click_button 'Transfer group'
        end

        within_testid('breadcrumb-links') do
          expect(page).to have_content(target_group.name) if target_group
          expect(page).to have_content(selected_group.name)
        end

        if target_group
          expect(current_url).to include("#{target_group.path}/#{selected_group_path}")
        else
          expect(current_url).to include(selected_group_path)
        end
      end
    end

    context 'when transfering from a subgroup' do
      let(:selected_group) { create(:group, path: 'foo-subgroup', parent: group) }

      context 'when transfering to no parent group' do
        let(:target_group) { nil }

        it_behaves_like 'can transfer the group'
      end

      context 'when transfering to a parent group' do
        let(:target_group) { create(:group, path: 'foo-parentgroup') }

        before do
          target_group.add_owner(user)
        end

        it_behaves_like 'can transfer the group'
      end
    end

    context 'when transfering from a root group to a parent group' do
      let(:selected_group) { create(:group, path: 'foo-rootgroup') }
      let(:target_group) { group }

      it_behaves_like 'can transfer the group'
    end
  end

  describe 'enable email notifications' do
    it 'is available' do
      visit edit_group_path(group)

      expect(page).to have_selector('#group_emails_enabled')
    end

    it 'accepts the changed state' do
      visit edit_group_path(group)
      uncheck 'group_emails_enabled'

      expect { save_permissions_group }.to change { updated_emails_enabled? }.to(false)
    end
  end

  describe 'prevent sharing outside group hierarchy setting' do
    it 'updates the setting' do
      visit edit_group_path(group)

      check 'group_prevent_sharing_groups_outside_hierarchy'

      expect { save_permissions_group }.to change {
        group.reload.prevent_sharing_groups_outside_hierarchy
      }.to(true)
    end

    it 'is not present for a subgroup' do
      subgroup = create(:group, parent: group)
      visit edit_group_path(subgroup)

      expect(page).to have_text "Permissions"
      expect(page).not_to have_selector('#group_prevent_sharing_groups_outside_hierarchy')
    end
  end

  describe 'group README', :js do
    context 'with gitlab-profile project and README.md' do
      let_it_be(:project) { create(:project, :readme, namespace: group) }

      it 'renders link to Group README and navigates to it on click' do
        visit edit_group_path(group)
        wait_for_requests

        click_link('README')
        wait_for_requests

        expect(page).to have_current_path(project_blob_path(project, "#{project.default_branch}/README.md"))
        expect(page).to have_text('README.md')
      end
    end

    context 'with gitlab-profile project and no README.md' do
      let_it_be(:project) { create(:project, path: 'gitlab-profile', namespace: group) }

      it 'renders Add README button and allows user to create a README via the IDE' do
        visit edit_group_path(group)
        wait_for_requests

        expect(page).not_to have_selector('.ide')

        click_button('Add README')

        accept_gl_confirm("This will create a README.md for project #{group.readme_project.present.path_with_namespace}.", button_text: 'Add README')
        wait_for_requests

        expect(page).to have_current_path("/-/ide/project/#{group.readme_project.present.path_with_namespace}/edit/main/-/README.md/")

        within_web_ide do
          expect(page).to have_text('README.md')
        end
      end
    end

    context 'with no gitlab-profile project and no README.md' do
      it 'renders Add README button and allows user to create both the gitlab-profile project and README via the IDE' do
        visit edit_group_path(group)
        wait_for_requests

        expect(page).not_to have_selector('.ide')

        click_button('Add README')

        accept_gl_confirm("This will create a project #{group.full_path}/gitlab-profile and add a README.md.", button_text: 'Create and add README')
        wait_for_requests

        expect(page).to have_current_path("/-/ide/project/#{group.full_path}/gitlab-profile/edit/main/-/README.md/")

        within_web_ide do
          expect(page).to have_text('README.md')
        end
      end
    end
  end

  describe 'archive', :js do
    let_it_be_with_reload(:ancestor) { group }
    let_it_be_with_reload(:subgroup) { create(:group, parent: ancestor) }

    shared_examples 'does not render archive settings when `archive_group` flag is disabled' do
      before do
        stub_feature_flags(archive_group: false)

        visit edit_group_path(subgroup)
      end

      specify { expect(page).not_to have_button(s_('GroupProjectArchiveSettings|Archive')) }
      specify { expect(page).not_to have_button(s_('GroupProjectUnarchiveSettings|Unarchive')) }
    end

    context 'when group is archived' do
      before do
        subgroup.namespace_settings.update!(archived: true)

        visit edit_group_path(subgroup)
      end

      it 'can unarchive group', :aggregate_failures do
        click_button s_('GroupProjectUnarchiveSettings|Unarchive')

        expect(page).to have_current_path(group_path(subgroup))
        expect(page.body).not_to include(safe_format(
          _('This group is archived. Its subgroups, projects, and data are %{strong_open}read-only%{strong_close}.'),
          tag_pair(tag.strong, :strong_open, :strong_close)
        ))
      end
    end

    context 'when ancestor is archived' do
      before do
        ancestor.namespace_settings.update!(archived: true)

        visit edit_group_path(subgroup)
      end

      it 'cannot archive or unarchive group', :aggregate_failures do
        expect(page).not_to have_button(s_('GroupProjectArchiveSettings|Archive'))
        expect(page).not_to have_button(s_('GroupProjectUnarchiveSettings|Unarchive'))
      end
    end

    context 'when group and parent is not archived' do
      before do
        visit edit_group_path(subgroup)
      end

      it 'can archive group', :aggregate_failures do
        click_button s_('GroupProjectArchiveSettings|Archive')

        expect(page).to have_current_path(group_path(subgroup))
        expect(page.body).to include(safe_format(
          _('This group is archived. Its subgroups, projects, and data are %{strong_open}read-only%{strong_close}.'),
          tag_pair(tag.strong, :strong_open, :strong_close)
        ))
      end

      it_behaves_like 'does not render archive settings when `archive_group` flag is disabled'
    end
  end

  describe 'group deletion', :js, :freeze_time do
    def remove_with_confirm(button_text, confirm_with, confirm_button_text = 'Confirm')
      click_button button_text
      fill_in 'confirm_name_input', with: confirm_with
      click_button confirm_button_text
    end

    before do
      stub_application_setting(deletion_adjourned_period: 7)
    end

    context 'when group is not marked for deletion' do
      before do
        visit edit_group_path(group)
      end

      it 'allows delayed deletion' do
        remove_with_confirm('Delete', group.path)

        expect(page).to have_content "This group and its subgroups and projects are pending deletion, and will be deleted on #{permanent_deletion_date_formatted}."
      end
    end

    context 'when group is marked for deletion' do
      before do
        create(:group_deletion_schedule, group: group)
      end

      context 'when "Allow immediate deletion" setting is enabled' do
        before do
          stub_application_setting(usage_ping_enabled: true)
          visit edit_group_path(group)
        end

        it 'allows immediate deletion', :sidekiq_inline do
          expect { remove_with_confirm('Delete immediately', group.path) }.to change { Group.count }.by(-1)

          expect(page).to have_content "Group '#{group.name}' is being deleted"
        end
      end

      context 'when there are subgroups and projects' do
        let_it_be(:subgroup) { create(:group, parent: group) }
        let_it_be(:project) { create(:project, namespace: group) }

        it 'does not allow immediate deletion of subgroup' do
          visit edit_group_path(subgroup)

          expect(page).not_to have_button('Delete immediately')
          expect(page).to have_content "This group will be deleted on #{permanent_deletion_date_formatted(group)} because its parent group is scheduled for deletion."
        end

        it 'does not allow immediate deletion of project' do
          visit edit_project_path(project)

          expect(page).not_to have_button('Delete immediately')
          expect(page).to have_content "This project will be deleted on #{permanent_deletion_date_formatted(group)} because its parent group is scheduled for deletion."
        end
      end

      context 'when "Allow immediate deletion" setting is disabled' do
        before do
          stub_application_setting(allow_immediate_namespaces_deletion: false)
        end

        context 'when allow_immediate_namespaces_deletion feature flag is disabled' do
          before do
            stub_feature_flags(allow_immediate_namespaces_deletion: false)
            visit edit_group_path(group)
          end

          it 'allows immediate deletion', :sidekiq_inline do
            expect { remove_with_confirm('Delete immediately', group.path) }.to change { Group.count }.by(-1)

            expect(page).to have_content "Group '#{group.name}' is being deleted"
          end
        end

        it 'allows immediate deletion for admins', :enable_admin_mode, :sidekiq_inline do
          sign_in(admin)
          visit edit_group_path(group)

          expect { remove_with_confirm('Delete immediately', group.path) }.to change { Group.count }.by(-1)

          expect(page).to have_content "Group '#{group.name}' is being deleted"
        end

        it 'does not allow immediate deletion' do
          visit edit_group_path(group)

          expect(page).not_to have_button('Delete immediately')
          expect(page).to have_content "This group and its subgroups and projects are pending deletion, and will be deleted on #{permanent_deletion_date_formatted(group)}."
        end
      end
    end
  end

  describe 'update pages access control' do
    let_it_be(:group) { create(:group, owners: [user]) }
    let_it_be(:project) { create(:project, :pages_published, namespace: group, pages_access_level: ProjectFeature::PUBLIC) }

    before do
      stub_pages_setting(access_control: true, enabled: true)
      allow(::Gitlab::Pages).to receive(:access_control_is_forced?).and_return(false)
    end

    context 'when group owner changes forced access control settings' do
      context 'when group access control is being enabled' do
        it 'project access control should be enforced' do
          visit edit_group_path(group)

          check 'group_force_pages_access_control'

          expect { save_permissions_group }.to change {
            project.private_pages?
          }.from(false).to(true)
        end
      end
    end
  end

  describe 'step-up authentication settings', :js do
    context 'with configured step-up omniauth providers' do
      let_it_be(:omniauth_provider_config_oidc) do
        GitlabSettings::Options.new(
          name: 'openid_connect',
          label: 'OpenID Connect',
          step_up_auth: {
            namespace: {
              id_token: {
                required: { acr: 'gold' }
              }
            }
          }
        )
      end

      before do
        stub_omniauth_setting(enabled: true, providers: [omniauth_provider_config_oidc])
        allow(Devise).to receive(:omniauth_providers).and_return([omniauth_provider_config_oidc.name])
      end

      context 'when group has no parent' do
        it 'allows configuring step-up authentication' do
          visit edit_group_path(group)

          within_testid('permissions-settings') do
            expect(page).to have_content('Step-up authentication')
            expect(page).to have_select('Step-up authentication', disabled: false, with_options: ['OpenID Connect'])
          end
        end

        it 'can select and save step-up authentication provider' do
          visit edit_group_path(group)

          within_testid('permissions-settings') do
            select 'OpenID Connect', from: 'group_step_up_auth_required_oauth_provider'
            click_button 'Save changes'
          end

          expect(page).to have_text("Group '#{group.name}' was successfully updated")
          expect(group.reload.namespace_settings.step_up_auth_required_oauth_provider).to eq('openid_connect')
        end

        it 'can disable step-up authentication' do
          group.namespace_settings.update!(step_up_auth_required_oauth_provider: 'openid_connect')

          visit edit_group_path(group)

          within_testid('permissions-settings') do
            select 'Disabled', from: 'group_step_up_auth_required_oauth_provider'
            click_button 'Save changes'
          end

          expect(page).to have_text("Group '#{group.name}' was successfully updated")
          expect(group.reload.namespace_settings.step_up_auth_required_oauth_provider).to be_nil
        end
      end

      context 'when group inherits step-up authentication from parent' do
        let_it_be_with_reload(:grandparent_group) { create(:group, owners: [user]) }
        let_it_be_with_reload(:parent_group) { create(:group, parent: grandparent_group, owners: [user]) }
        let_it_be_with_reload(:child_group) { create(:group, parent: parent_group, owners: [user]) }

        before do
          parent_group.namespace_settings.update!(step_up_auth_required_oauth_provider: 'openid_connect')
        end

        it 'shows complete inheritance alert with parent group name and guidance' do
          visit edit_group_path(child_group)

          within_testid('permissions-settings') do
            expect(page).to have_content('Step-up authentication')

            # Check the alert exists and has correct properties
            alert = find_by_testid('step-up-auth-inheritance-alert')
            expect(alert).to be_present
            expect(alert[:class]).to include('gl-alert-info')
            expect(alert).to have_content("Step-up authentication is inherited from parent group \"#{parent_group.name}\"")
          end
        end

        it 'disables form controls and shows inherited value with proper accessibility' do
          visit edit_group_path(child_group)

          within_testid('permissions-settings') do
            expect(page).to have_content('Step-up authentication')

            # Check inheritance alert with complete messaging
            expect(find_by_testid('step-up-auth-inheritance-alert'))
              .to have_content("Step-up authentication is inherited from parent group \"#{parent_group.name}\"")

            # Form controls should be disabled with correct inherited value
            expect(page).to have_select('Step-up authentication', disabled: true, selected: 'OpenID Connect')

            child_group.namespace_settings.update!(step_up_auth_required_oauth_provider: nil)
          end
        end
      end
    end

    context 'without configured step-up omniauth providers' do
      before do
        stub_omniauth_setting(enabled: true, providers: [])
        allow(Devise).to receive(:omniauth_providers).and_return([])
      end

      it 'shows only disabled option' do
        visit edit_group_path(group)

        within_testid('permissions-settings') do
          expect(page).to have_content('Step-up authentication')
          expect(page).to have_select('Step-up authentication', disabled: false, selected: 'Disabled', with_options: [])
        end
      end
    end
  end

  def update_path(new_group_path)
    visit edit_group_path(group)

    within_testid('advanced-settings-content') do
      fill_in 'group_path', with: new_group_path
      click_button 'Change group URL'
    end
  end

  def save_general_group
    within_testid('general-settings') do
      click_button 'Save changes'
    end
  end

  def save_permissions_group
    within_testid('permissions-settings') do
      click_button 'Save changes'
    end
  end

  def updated_emails_enabled?
    group.reload.clear_memoization(:emails_enabled_memoized)
    group.emails_enabled?
  end
end
